using System;
using System.Diagnostics;
using System.Xml;
using CKS.EBE.API;
using Microsoft.SharePoint;
using System.IO;
using System.Web;
using HtmlAgilityPack;
using Microsoft.SharePoint.Utilities;

namespace CKS.EBE
{
    public class RssFeed
    {
        public static string GetFeed()
        {
            try
            {
                var site = SPContext.Current.Site;
                var web = SPContext.Current.Web;
                SPList postsList = Helper.GetList(web, Localization.BlogList.Posts);

                var xmlDocument = new XmlDocument();
                var nsm = new XmlNamespaceManager(xmlDocument.NameTable);
                nsm.AddNamespace("dc", "http://purl.org/dc/elements/1.1/");

                using (var memoryStream = new MemoryStream())
                {
                    postsList.WriteRssFeed(memoryStream);
                    memoryStream.Position = 0;
                    xmlDocument.Load(memoryStream);
                }
                string sUrl = site.Url;
                if (sUrl.EndsWith("/")) sUrl = sUrl.Substring(0, sUrl.Length - 1);

                XmlNodeList oItems = xmlDocument.SelectNodes("//item");
                for (int c = 0; c < oItems.Count; c++)
                {
                    oItems[c].ParentNode.RemoveChild(oItems[c]);
                }

                CreateHeader(xmlDocument, site, web);

                var oQuery = new SPQuery
                                 {
                                     ViewFields = "<FieldRef Name='Title'/><FieldRef Name='ID'/><FieldRef Name='Author'/><FieldRef Name='Body'/><FieldRef Name='NumComments'/><FieldRef Name='PublishedDate'/><FieldRef Name='PostCategory'/><FieldRef Name='FileDirRef'/><FieldRef Name='BlogTitleForUrl'/><FieldRef Name='CategoriesAsText' Nullable='True' />",
                                     Query = "<OrderBy><FieldRef Name='PublishedDate' Ascending='FALSE'/></OrderBy>"
                                 };

                DateTime now =  DateTime.Now;
                if (HttpContext.Current.Request["cat"] != null)
                {
                    oQuery.ViewFields += "<FieldRef Name='Categories'/>";
                    oQuery.Query +=
                        string.Format("<Where><And><Leq><FieldRef Name='PublishedDate' /><Value Type='DateTime'>{0}</Value></Leq>" +
                                      "<Eq><FieldRef Name='Categories'/><Value Type='MultiLookup'>{1}</Value></Eq></And></Where>",
                                      SPUtility.CreateISO8601DateTimeFromSystemDateTime(now), HttpContext.Current.Request["cat"]);
                }
                else
                {
                    // only show published posts
                    oQuery.Query += string.Format("<Where><Leq><FieldRef Name='PublishedDate' /><Value Type='DateTime'>{0}</Value></Leq></Where>",
                                                  SPUtility.CreateISO8601DateTimeFromSystemDateTime(now));
                }

                // RowLimit should be set in the Posts List RSS Settings !
                oQuery.RowLimit = (uint) (string.IsNullOrEmpty(BlogContext.Current.Settings.RssItemCount) ? 10 : int.Parse(BlogContext.Current.Settings.RssItemCount));

                SPListItemCollection oPostItems = postsList.GetItems(oQuery);

                foreach (SPListItem oItem in oPostItems)
                {
                    CreateItem(xmlDocument, sUrl, oItem);
                }

                FixDescriptions(xmlDocument, site, web);

                return xmlDocument.OuterXml;
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Creating RSS feed failed.....");
                Debug.WriteLine(ex);
                return null;
            }
        }

        private static void CreateHeader(XmlNode oDoc, SPSite site, SPWeb web)
        {
            var oHeadItem = (XmlElement)oDoc.SelectSingleNode("//channel/title");
            oHeadItem.InnerText = web.Title;

            oHeadItem = (XmlElement)oDoc.SelectSingleNode("//channel/link");
            oHeadItem.InnerText = web.Url;

            oHeadItem = (XmlElement)oDoc.SelectSingleNode("//channel/generator");
            oHeadItem.InnerText = "SharePoint CKS:EBE";

            oHeadItem = (XmlElement)oDoc.SelectSingleNode("//channel/image/title");
            oHeadItem.InnerText = web.Title;

            oHeadItem = (XmlElement)oDoc.SelectSingleNode("//channel/image/url");
            oHeadItem.InnerText = (oHeadItem.InnerText.StartsWith("http")) ? oHeadItem.InnerText : site.MakeFullUrl(oHeadItem.InnerText);

            oHeadItem = (XmlElement)oDoc.SelectSingleNode("//channel/image/link");
            oHeadItem.InnerText = web.Url;
        }

        private static void CreateItem(XmlDocument oDoc, string sUrl, SPItem oItem)
        {
            XmlElement oElement = oDoc.CreateElement("item");
            var oParent = (XmlElement)oDoc.DocumentElement.FirstChild;
            oParent.AppendChild(oElement);

            AddValue(oElement, "title", (string)oItem["Title"]);

            string sLink = XslMethods.createPostUrl(((DateTime)oItem["Created"]).ToString("yyyy-MM-dd HH:mm"), (string)oItem["BlogTitleForUrl"], oItem.ID.ToString());
            AddValue(oElement, "link", HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + sLink);
            XmlElement oGuid = AddValue(oElement, "guid", sLink);
            oGuid.SetAttribute("isPermaLink", bool.FalseString);

            var sBody = (string)oItem["Body"];
            XmlCDataSection oSection = oDoc.CreateCDataSection(sBody);
            XmlElement oDescription = oDoc.CreateElement("description");
            oDescription.AppendChild(oSection);
            oElement.AppendChild(oDescription);

            XmlElement oCreator = oDoc.CreateElement("dc", "creator", "http://purl.org/dc/elements/1.1/");
            oCreator.InnerText = ((string)oItem["Author"]).Split('#')[1];
            oElement.AppendChild(oCreator);

            var published = (DateTime)oItem["PublishedDate"];
            AddValue(oElement, "pubDate", published.ToString("R"));

            var sCategories = (string)oItem["CategoriesAsText"];
            if (sCategories != null)
            {
                AddCategories(oDoc, sUrl, oElement, sCategories);
            }
        }

        private static void AddCategories(XmlDocument oDoc, string sUrl, XmlNode oElement, string sCategories)
        {
            string[] arCats = sCategories.Split(';');
            foreach (string sCategory in arCats)
            {
                if (sCategory.Trim() != "")
                {
                    XmlElement oCat = oDoc.CreateElement("category");
                    oCat.InnerText = sCategory.Trim();
                    oCat.SetAttribute("domain", sUrl + XslMethods.createTagUrl(sCategory.Trim()));
                    oElement.AppendChild(oCat);
                }
            }
        }

        private static void FixDescriptions(XmlDocument oDoc, SPSite oSite, SPWeb web)
        {
            XmlNodeList oList = oDoc.DocumentElement.SelectNodes("//item/description");
            foreach (XmlElement oDescription in oList)
            {
                var oHtml = new HtmlDocument();
                var oSection = (XmlCDataSection)oDescription.FirstChild;
                oHtml.LoadHtml(oSection.InnerText);
                HtmlNodeCollection oCollection = oHtml.DocumentNode.SelectNodes("//img | //a");

                if (oCollection != null)
                {
                    foreach (var oNode in oCollection)
                    {
                        FixUrlForFeed(oSite, oNode);
                    }

                    GetBodyText(oHtml, oSection, oSite, web, oDoc);
                }
                else
                {
                    GetBodyText(oHtml, oSection, oSite, web, oDoc);
                }

            }
        }

        /// <summary>
        /// change relative to absolute urls for the tags src and href
        /// </summary>
        /// <param name="oSite"></param>
        /// <param name="oNode"></param>
        private static void FixUrlForFeed(SPSite oSite, HtmlNode oNode)
        {
            FixAttribUrl(oNode.Attributes["src"], oSite);
            FixAttribUrl(oNode.Attributes["href"], oSite);
        }

        /// <summary>
        /// make url absolute, if it starts with /
        /// </summary>
        /// <param name="htmlAttribute"></param>
        /// <param name="oSite"></param>
        private static void FixAttribUrl(HtmlAttribute htmlAttribute, SPSite oSite)
        {
            if (htmlAttribute != null && htmlAttribute.Value.StartsWith("/"))
            {
                htmlAttribute.Value = oSite.MakeFullUrl(htmlAttribute.Value);
            }
        }

        private static void GetBodyText(HtmlDocument oHtml, XmlNode oSection, SPSite site, SPWeb web, XmlDocument oDoc)
        {
            // deal with possible enclosures before the body is shortened
            AddEnclosure(oSection, oDoc, site);

            if (BlogContext.Current.Settings.TrimPostBodyOnRss)
            {
                if (oHtml.DocumentNode.InnerText.Length > 300)
                {
                    oSection.InnerText = oHtml.DocumentNode.InnerText.Substring(0, 300);
                    oSection.InnerText += Localization.GetResource("RssMore", web.Language);
                }
                else
                {
                    oSection.InnerText = oHtml.DocumentNode.InnerText;
                }
            }
            else
            {
                oSection.InnerText = oHtml.DocumentNode.InnerHtml;
            }
        }

        /// <summary>
        /// add enclosure tag for mp3, mov, wmv, mp4, pdf to item
        /// </summary>
        /// <param name="oSection"></param>
        /// <param name="oDoc"></param>
        /// <param name="site"></param>
        private static void AddEnclosure(XmlNode oSection, XmlDocument oDoc, SPSite site)
        {
            // only one enclosure is supported. so take only the first
            try
            {
                string type = string.Empty;
                var doc = new HtmlDocument();
                doc.LoadHtml(oSection.InnerText);
                HtmlNodeCollection oCollection = doc.DocumentNode.SelectNodes("//a");

                if (oCollection == null) return;
                
                foreach (HtmlNode oNode in oCollection)
                {
                    FixAttribUrl(oNode.Attributes["href"], site);
                    string linkUrl = oNode.Attributes["href"].Value;
                    // make linkUrl absolute, if it is a relative Url
                    //if (linkUrl.StartsWith("/")) linkUrl = SPContext.Current.Site.MakeFullUrl(linkUrl);
                    string extension = Path.GetExtension(linkUrl);
                    switch (extension)
                    {
                        case ".mp3":
                            type = "audio/mpeg";
                            break;
                        case ".mov":
                            type = "video/quicktime";
                            break;
                        case ".wmv":
                            type = "video/wmv";
                            break;
                        case ".mp4":
                            type = "video/mp4";
                            break;
                        case ".pdf":
                            type = "application/pdf";
                            break;
                    }

                    if (string.IsNullOrEmpty(type) == false)
                    {
                        // add enclosure as new tag below item
                        XmlElement oEnclosure = oDoc.CreateElement("enclosure");
                        oEnclosure.SetAttribute("url", linkUrl);
                        oEnclosure.SetAttribute("type", type);
                        // find item, to add the enclosure as new child to it
                        XmlNode item = oSection.ParentNode.ParentNode;
                        item.AppendChild(oEnclosure);
                        break;
                    }

                    //if (linkUrl.EndsWith(".mp3", StringComparison.CurrentCultureIgnoreCase))
                    //{
                    //    type = "audio/mpeg";
                    //    break;
                    //}
                    //if (linkUrl.EndsWith(".mov", StringComparison.CurrentCultureIgnoreCase))
                    //{
                    //    type = "video/quicktime";
                    //    break;
                    //}
                    //if (linkUrl.EndsWith(".wmv", StringComparison.CurrentCultureIgnoreCase))
                    //{
                    //    type = "video/wmv";
                    //    break;
                    //}
                    //if (linkUrl.EndsWith(".mp4", StringComparison.CurrentCultureIgnoreCase))
                    //{
                    //    type = "video/mp4";
                    //    break;
                    //}
                    //if (linkUrl.EndsWith(".pdf", StringComparison.CurrentCultureIgnoreCase))
                    //{
                    //    type = "application/pdf";
                    //    break;
                    //}
                }

                //if (string.IsNullOrEmpty(type) == false)
                //{
                //    // add enclosure as new tag below item
                //    XmlElement oEnclosure = oDoc.CreateElement("enclosure");
                //    oEnclosure.SetAttribute("url", linkUrl);
                //    oEnclosure.SetAttribute("type", type);
                //    // find item, to add the enclosure as new child to it
                //    XmlNode item = oSection.ParentNode.ParentNode;
                //    item.AppendChild(oEnclosure);
                //}
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error in AddEnclosure: " + ex);
            }
        }

        protected static XmlElement AddValue(XmlElement parent, string name, string value)
        {
            XmlElement oValue = parent.OwnerDocument.CreateElement(name);
            parent.AppendChild(oValue);
            oValue.InnerText = value;

            return oValue;
        }
    }
}
